/*  GStruct: data structure helpers for Java
 *  Copyright (C) 2011 Giulio Franco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package gstruct.single;

/**
 * Generic option data type
 * @author Giulio Franco <giulio_franco@msn.com>
 *
 * @param <A>	First possible content type
 * @param <B>	Second possible content type
 */
public class Option<A, B> {
	private final A a;
	private final B b;
	
	private Option(A a, B b) {
		this.a = a;
		this.b = b;
	}
	
	public static <A,B> Option<A,B> createA(A object) {
		if (object == null) {
			throw new NullPointerException();
		}
		return new Option<A,B>(object, null);
	}
	
	public static <A,B> Option<A,B> createB(B object) {
		if (object == null) {
			throw new NullPointerException();
		}
		return new Option<A,B>(null, object);
	}
	
	@SuppressWarnings("unchecked")
	public static <A,B> Option<A,B> create(Class<A> aClass, Class<B> bClass, Object o) {
		boolean isA = aClass.isInstance(o);
		boolean isB = bClass.isInstance(o);
		
		if (isA && isB) {
			throw new IllegalArgumentException("Object of type " + o.getClass().getName() + " is both " + aClass.getName() + " and " + bClass.getName() + ". Use createA, createB or create(Class, Class, Object, Class) constructors.");
		} else if (isA) {
			return createA((A)o);
		} else if (isB) {
			return createB((B)o);
		} else {
			throw new ClassCastException("Object of type " + o.getClass().getName() + " cannot be cast to either " + aClass.getName() + " or " + bClass.getName());
		}
	}
	
	@SuppressWarnings("unchecked")
	public static <A,B> Option<A,B> create(Class<A> aClass, Class<B> bClass, Object o, Class<?> defaultClass) {
		boolean isA = aClass.isInstance(o);
		boolean isB = bClass.isInstance(o);
		
		if (isA && isB) {
			if (defaultClass == aClass) {
				return createA((A)o);
			} else if (defaultClass == bClass){
				return createB((B)o);
			} else {
				throw new IllegalArgumentException("Default class " + defaultClass.getName() + " is neither " + aClass.getName() + " nor " + bClass.getName());
			}
		} else if (isA) {
			return createA((A)o);
		} else if (isB) {
			return createB((B)o);
		} else {
			throw new ClassCastException("Object of type " + o.getClass().getName() + " cannot be cast to either " + aClass.getName() + " or " + bClass.getName());
		}
	}
	
	public boolean isA() {
		return b == null;
	}
	
	public boolean isB() {
		return a == null;
	}
	
	public A getA() {
		return a;
	}
	
	public B getB() {
		return b;
	}
}
